package com.newfiber.workflow.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.flowable.common.engine.api.async.AsyncTaskExecutor;
import org.flowable.common.engine.impl.cfg.IdGenerator;
import org.flowable.common.engine.impl.persistence.StrongUuidGenerator;
import org.flowable.common.spring.AutoDeploymentStrategy;
import org.flowable.common.spring.CommonAutoDeploymentProperties;
import org.flowable.common.spring.async.SpringAsyncTaskExecutor;
import org.flowable.engine.ProcessEngine;
import org.flowable.http.common.api.client.FlowableHttpClient;
import org.flowable.job.service.impl.asyncexecutor.AsyncExecutor;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.FlowableAutoDeploymentProperties;
import org.flowable.spring.boot.FlowableHttpProperties;
import org.flowable.spring.boot.FlowableJobConfiguration;
import org.flowable.spring.boot.FlowableMailProperties;
import org.flowable.spring.boot.FlowableProperties;
import org.flowable.spring.boot.ProcessEngineAutoConfiguration;
import org.flowable.spring.boot.app.AppEngineServicesAutoConfiguration;
import org.flowable.spring.boot.app.FlowableAppProperties;
import org.flowable.spring.boot.condition.ConditionalOnProcessEngine;
import org.flowable.spring.boot.eventregistry.FlowableEventRegistryProperties;
import org.flowable.spring.boot.idm.FlowableIdmProperties;
import org.flowable.spring.boot.process.FlowableProcessProperties;
import org.flowable.spring.boot.process.Process;
import org.flowable.spring.boot.process.ProcessAsync;
import org.flowable.spring.boot.process.ProcessAsyncHistory;
import org.flowable.spring.configurator.DefaultAutoDeploymentStrategy;
import org.flowable.spring.configurator.ResourceParentFolderAutoDeploymentStrategy;
import org.flowable.spring.configurator.SingleResourceAutoDeploymentStrategy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

/**
 * @author : X.K
 * @since : 2023/7/10 下午5:31
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProcessEngine
@EnableConfigurationProperties({
	FlowableAutoDeploymentProperties.class,
	FlowableProperties.class,
	FlowableMailProperties.class,
	FlowableHttpProperties.class,
	FlowableProcessProperties.class,
	FlowableAppProperties.class,
	FlowableIdmProperties.class,
	FlowableEventRegistryProperties.class
})
@AutoConfigureAfter(value = {
	ProcessEngineAutoConfiguration.class,
}, name = {
	"org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration"
})
@AutoConfigureBefore({
	AppEngineServicesAutoConfiguration.class,
})
@Import({
	FlowableJobConfiguration.class
})
public class CustomProcessEngineAutoConfiguration extends ProcessEngineAutoConfiguration {

	public CustomProcessEngineAutoConfiguration(
		FlowableProperties flowableProperties,
		FlowableProcessProperties processProperties,
		FlowableAppProperties appProperties,
		FlowableIdmProperties idmProperties,
		FlowableEventRegistryProperties eventProperties,
		FlowableMailProperties mailProperties,
		FlowableHttpProperties httpProperties,
		FlowableAutoDeploymentProperties autoDeploymentProperties) {
		super(flowableProperties, processProperties, appProperties, idmProperties, eventProperties,
			mailProperties, httpProperties, autoDeploymentProperties);
	}

	@Bean
	@Primary
	@Override
	@SuppressWarnings({"SpringJavaInjectionPointsAutowiringInspection", "unchecked", "rawtypes"})
	public SpringProcessEngineConfiguration springProcessEngineConfiguration(DataSource dataSource, PlatformTransactionManager platformTransactionManager,
		ObjectProvider<ObjectMapper> objectMapperProvider,
		@Process ObjectProvider<IdGenerator> processIdGenerator,
		ObjectProvider<IdGenerator> globalIdGenerator,
		@ProcessAsync ObjectProvider<AsyncExecutor> asyncExecutorProvider,
		@Qualifier("applicationTaskExecutor") ObjectProvider<AsyncListenableTaskExecutor> applicationTaskExecutorProvider,
		@ProcessAsyncHistory ObjectProvider<AsyncExecutor> asyncHistoryExecutorProvider,
		ObjectProvider<AsyncListenableTaskExecutor> taskExecutor,
		@Process ObjectProvider<AsyncListenableTaskExecutor> processTaskExecutor,
		@Qualifier("flowableAsyncTaskInvokerTaskExecutor") ObjectProvider<AsyncTaskExecutor> asyncTaskInvokerTaskExecutor,
		ObjectProvider<FlowableHttpClient> flowableHttpClient,
		ObjectProvider<List<AutoDeploymentStrategy<ProcessEngine>>> processEngineAutoDeploymentStrategies) throws IOException {

		SpringProcessEngineConfiguration conf = new CustomSpringProcessEngineConfiguration();
		List<Resource> resources = this.discoverDeploymentResources(this.flowableProperties.getProcessDefinitionLocationPrefix(), this.flowableProperties.getProcessDefinitionLocationSuffixes(), this.flowableProperties.isCheckProcessDefinitions());
		if (resources != null && !resources.isEmpty()) {
			conf.setDeploymentResources(resources.toArray(new Resource[0]));
			conf.setDeploymentName(this.flowableProperties.getDeploymentName());
		}

		AsyncExecutor springAsyncExecutor = asyncExecutorProvider.getIfUnique();
		if (springAsyncExecutor != null) {
			conf.setAsyncExecutor(springAsyncExecutor);
		}

		AsyncListenableTaskExecutor asyncTaskExecutor = this.getIfAvailable(processTaskExecutor, taskExecutor);
		if (asyncTaskExecutor == null) {
			asyncTaskExecutor = applicationTaskExecutorProvider.getObject();
		}

		if (asyncTaskExecutor != null) {
			AsyncTaskExecutor flowableTaskExecutor = new SpringAsyncTaskExecutor(asyncTaskExecutor);
			conf.setAsyncTaskExecutor(flowableTaskExecutor);
			conf.setAsyncHistoryTaskExecutor(flowableTaskExecutor);
		}

		AsyncExecutor springAsyncHistoryExecutor = asyncHistoryExecutorProvider.getIfUnique();
		if (springAsyncHistoryExecutor != null) {
			conf.setAsyncHistoryEnabled(true);
			conf.setAsyncHistoryExecutor(springAsyncHistoryExecutor);
		}

		AsyncTaskExecutor taskInvokerTaskExecutor = asyncTaskInvokerTaskExecutor.getIfAvailable();
		if (taskInvokerTaskExecutor != null) {
			conf.setAsyncTaskInvokerTaskExecutor(taskInvokerTaskExecutor);
		}

		ObjectMapper objectMapper = objectMapperProvider.getIfAvailable();
		if (objectMapper != null) {
			conf.setObjectMapper(objectMapper);
		}

		this.configureSpringEngine(conf, platformTransactionManager);
		this.configureEngine(conf, dataSource);
		conf.setDeploymentName(this.defaultText(this.flowableProperties.getDeploymentName(), conf.getDeploymentName()));
		conf.setDisableIdmEngine(!this.flowableProperties.isDbIdentityUsed() || !this.idmProperties.isEnabled());
		conf.setDisableEventRegistry(!this.eventProperties.isEnabled());
		conf.setAsyncExecutorActivate(this.flowableProperties.isAsyncExecutorActivate());
		conf.setAsyncHistoryExecutorActivate(this.flowableProperties.isAsyncHistoryExecutorActivate());
		conf.setMailServerHost(this.mailProperties.getHost());
		conf.setMailServerPort(this.mailProperties.getPort());
		conf.setMailServerSSLPort(this.mailProperties.getSSLPort());
		conf.setMailServerUsername(this.mailProperties.getUsername());
		conf.setMailServerPassword(this.mailProperties.getPassword());
		conf.setMailServerDefaultFrom(this.mailProperties.getDefaultFrom());
		conf.setMailServerForceTo(this.mailProperties.getForceTo());
		conf.setMailServerUseSSL(this.mailProperties.isUseSsl());
		conf.setMailServerUseTLS(this.mailProperties.isUseTls());
		conf.setMailServerDefaultCharset(this.mailProperties.getDefaultCharset());
		conf.getHttpClientConfig().setUseSystemProperties(this.httpProperties.isUseSystemProperties());
		conf.getHttpClientConfig().setConnectionRequestTimeout(this.httpProperties.getConnectionRequestTimeout());
		conf.getHttpClientConfig().setConnectTimeout(this.httpProperties.getConnectTimeout());
		conf.getHttpClientConfig().setDisableCertVerify(this.httpProperties.isDisableCertVerify());
		conf.getHttpClientConfig().setRequestRetryLimit(this.httpProperties.getRequestRetryLimit());
		conf.getHttpClientConfig().setSocketTimeout(this.httpProperties.getSocketTimeout());
		conf.getHttpClientConfig().setHttpClient(flowableHttpClient.getIfAvailable());
		conf.setEnableProcessDefinitionHistoryLevel(this.processProperties.isEnableProcessDefinitionHistoryLevel());
		conf.setProcessDefinitionCacheLimit(this.processProperties.getDefinitionCacheLimit());
		conf.setEnableSafeBpmnXml(this.processProperties.isEnableSafeXml());
		conf.setEventRegistryStartProcessInstanceAsync(this.processProperties.isEventRegistryStartProcessInstanceAsync());
		conf.setEventRegistryUniqueProcessInstanceCheckWithLock(this.processProperties.isEventRegistryUniqueProcessInstanceCheckWithLock());
		conf.setEventRegistryUniqueProcessInstanceStartLockTime(this.processProperties.getEventRegistryUniqueProcessInstanceStartLockTime());
		conf.setHistoryLevel(this.flowableProperties.getHistoryLevel());
		conf.setActivityFontName(this.flowableProperties.getActivityFontName());
		conf.setAnnotationFontName(this.flowableProperties.getAnnotationFontName());
		conf.setLabelFontName(this.flowableProperties.getLabelFontName());
		conf.setFormFieldValidationEnabled(this.flowableProperties.isFormFieldValidationEnabled());
		conf.setEnableHistoryCleaning(this.flowableProperties.isEnableHistoryCleaning());
		conf.setHistoryCleaningTimeCycleConfig(this.flowableProperties.getHistoryCleaningCycle());
		conf.setCleanInstancesEndedAfter(this.flowableProperties.getHistoryCleaningAfter());
		conf.setCleanInstancesBatchSize(this.flowableProperties.getHistoryCleaningBatchSize());
		IdGenerator idGenerator = this.getIfAvailable(processIdGenerator, globalIdGenerator);
		if (idGenerator == null) {
			idGenerator = new StrongUuidGenerator();
		}

		conf.setIdGenerator(idGenerator);
		List<AutoDeploymentStrategy<ProcessEngine>> deploymentStrategies = processEngineAutoDeploymentStrategies.getIfAvailable();
		if (deploymentStrategies == null) {
			deploymentStrategies = new ArrayList();
		}

		CommonAutoDeploymentProperties deploymentProperties = this.autoDeploymentProperties.deploymentPropertiesForEngine("bpmn");
		deploymentStrategies.add(new DefaultAutoDeploymentStrategy(deploymentProperties));
		deploymentStrategies.add(new SingleResourceAutoDeploymentStrategy(deploymentProperties));
		deploymentStrategies.add(new ResourceParentFolderAutoDeploymentStrategy(deploymentProperties));
		conf.setDeploymentStrategies(deploymentStrategies);
		return conf;
	}
}
